#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include "utils/player/audio_player.h"
#include <stdbool.h>
#include "utils/str_util.h"

// SDL_mixer第三方库安装(SDL2类似)：
// 1️⃣MacOS系统通过包管理器Homebrew安装SDL_mixer第三方库：brew install sdl2_mixer；
// Windows系统通过MSYS2包管理器安装：pacman -S mingw-w64-x86_64-SDL2_mixer
// 2️⃣CMakeList.txt引入第三方库(在现代CMake（>=3.14）里，find_package(SDL2_mixer REQUIRED) 通常会导出一个目标（target）)：
// 有这个目标（target），直接target_link_libraries(main PUBLIC SDL2_mixer::SDL2_mixer)即可
// 无需再include_directories(${SDL2_MIXER_INCLUDE_DIRS})和target_link_libraries(main PRIVATE ${SDL2_MIXER_LIBRARIES})
// ✅即现代CMake的find_package能够返回目标target例如SDL2_mixer::SDL2_mixer，则CMakeList.txt只需下面两行来实现引入
// 1、find_package(SDL2_mixer REQUIRED)
// 2、target_link_libraries(main PUBLIC SDL2_mixer::SDL2_mixer)

static _Bool audio_initialized = false;
Mix_Music *playing_music;
Mix_MusicFinishedCallback play_finished_callback;

/**
 * 当前值设置音量
 * @param value 音量比例数值，value需确保范围在0-100，slider默认取值范围是：最小值0～最大值100
 * @return void
 */
void config_volume_with_value(int value) {
    if (value < 0 || value > 100) {
        printf("config_volume_with_value param value is out of range.\n");
        return;
    }
    float volume_percent_value = (float)value / 100.0f;
    config_volume(volume_percent_value);
}

/**
 * 设置音量
 * @param percent 音量比例
 * @return void
 */
void config_volume(float volume_percent_value) {
    if (volume_percent_value < 0 || volume_percent_value > 1) {
        printf("config_volume param volume_percent_value is out of range.\n");
        return;
    }

    // volume：0 = 静音，最大音量 MIX_MAX_VOLUME，取值范围 0 ~ MIX_MAX_VOLUME（默认 128）
    int volume = (int)(MIX_MAX_VOLUME * volume_percent_value);
    Mix_VolumeMusic(volume);
}


/**
 * 播放器是否无音乐，当播放结束时音乐被释放此时playing_music为NULL则为true
 * @param void
 * @return _Bool 是否无音乐
 */
_Bool is_empty_player()
{
    if (playing_music == NULL) {
        return true;
    }
    return false;
}

/**
 * 程序初始化时（只调用一次）
 * @param void
 * @return void
 */
void audio_init()
{
    if (!audio_initialized) {
        // 初始化 SDL 的底层子系统（音频、视频、输入设备、定时器等）。
        // 类似于“打开电源”，让 SDL 可以控制操作系统资源（比如声卡、显卡、键盘、鼠标）。
        // SDL_INIT_AUDIO 表示只初始化音频系统。
        // SDL_INIT_VIDEO：图形窗口
        // SDL_INIT_TIMER：定时器
        // SDL_INIT_GAMECONTROLLER：手柄
        // SDL_INIT_EVERYTHING：一次性全开
        int first_init = SDL_Init(SDL_INIT_AUDIO);
        if (first_init < 0) {//调用成功会返回 0（已初始化）
            printf("SDL_Init error: %s\n", SDL_GetError());
            return;
        }
        printf("SDL_Init first time: %d\n", first_init);//SDL_Init first time: 0
        // int second_init = SDL_Init(SDL_INIT_AUDIO);
        // printf("SDL_Init second time: %d\n", second_init);//SDL_Init second time: 0

        // 先初始化基础的SDL_Init(SDL_INIT_AUDIO)，然后再Mix_OpenAudio初始化混音器（SDL_mixer）
        if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {//重复调用不会报错，但浪费性能
            printf("Mix_OpenAudio error: %s\n", Mix_GetError());
            return;
        }
        // 参数	         类型	    含义	                           常见值
        // frequency	int	       采样率（Hz）—— 每秒采样次数	         44100（CD 质量）
        // format	    Uint16	   音频数据格式	                       MIX_DEFAULT_FORMAT（16 位有符号，系统默认）
        // channels	    int	       声道数	                          2（立体声）或 1（单声道）
        // chunksize	int	       音频缓冲区大小（每次混音读取样本数量）   2048 是常用值；越小延迟越低，但CPU开销略大


        audio_initialized = true;
    }
}

/**
 * 程序退出时调用，关闭音频系统
 * @param void
 * @return void
 */
void audio_deinit()
{
    if (audio_initialized) {
        stopPlayingMusic();
        // Mix_CloseAudio和Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);成对使用，不关闭可能导致程序退出后音频设备未释放。
        Mix_CloseAudio();
        SDL_Quit();
        audio_initialized = false;
        // SDL_Delay(100);//休眠100毫秒，延迟100毫秒
        // SDL_Quit();//清理 SDL 初始化的所有子系统（视频、音频、事件等）。必须在程序结束前调用，否则某些平台可能内存未完全释放。
    }
}

/**
 * 每首mp3播放结束后需调用该方法手动释放音乐
 * @param void
 * @return void
 */
void freePlayingMusic() {
    Mix_FreeMusic(playing_music);// 释放之前内存加载的音乐资源，否则会内存泄漏
    playing_music = NULL;
    play_finished_callback = NULL;
}

/**
 * 暂停当前正在播放的音乐
 * @param void
 * @return void
 */
void pausePlayingMusic() {
    Mix_PauseMusic();// 暂停播放
}

/**
 * 恢复继续播放
 * @param void
 * @return void
 */
void resumePlayingMusic() {
    Mix_ResumeMusic();// 恢复继续播放
}
/**
 * 把播放位置重置到开头（重新从头播放）
 * @param void
 * @return void
 */
void rewindPlayingMusic() {
    Mix_RewindMusic();// 把播放位置重置到开头（重新从头播放）
}

/**
 * 停止当前正在播放的音乐（立即停止播放）
 * @param void
 * @return void
 */
void stopPlayingMusic() {
    Mix_HaltMusic();// 停止播放
    freePlayingMusic();//❌ Mix_HaltMusic() 不会触发Mix_HookMusicFinished()播放结束回调，因此此处需要手动调用释放音乐的方法
}

/**
 * 是否正在播放mp3
 * @param void
 * @return _Bool 是否正在播放mp3
 */
_Bool isPlayingMusic() {
    return Mix_PlayingMusic();
}
/**
 * 是否出于暂停播放状态
 * @param void
 * @return _Bool 是否出于暂停播放状态
 */
_Bool isPausedMusic() {
    return Mix_PausedMusic();
}
/**
 * 若为暂停播放状态则继续播放，若为正在播放状态则暂停播放，其他状态不做处理
 * @param void
 * @return void
 */
void togglePlayStatusMusic() {
    if (is_empty_player()) {
        return;
    }
    if (isPausedMusic())
    {
        resumePlayingMusic();
    } else if (isPlayingMusic()) {
        pausePlayingMusic();
    }

}

/**
 * 通过Mix_HookMusicFinished(music_finished_callback);监听音乐播放结束回调
 * @param void
 * @return void
 */
static void music_finished_callback() {
    if (play_finished_callback != NULL)
    {
        play_finished_callback();
        play_finished_callback = NULL;
    }
    printf("music finished play\n");

    freePlayingMusic();
}

/**
 * 播放mp3文件
 * @param path	mp3文件路径
 * ❌SDL_mixer 在不同平台（macOS vs Windows）路径解析机制有差异：
 * MacOS系统传file相对路径"assets/audio/SmileThree.mp3"是从项目根目录开始，但在Windows系统对应路径实际为main.exe所在路径：
 * 即F:\c\EmbeddedDevelopment\lv_port_pc_vscode\bin，导致实际路径为：
 * F:\c\EmbeddedDevelopment\lv_port_pc_vscode\bin\assets\audio\SmileThree.mp3
 * ❌导致Windows系统报错：Mix_LoadMUS error: Couldn't open 'assets/audio/SmileThree.mp3'
 * ✅解决办法：Windows系统传file相对路径需多加一个..表示上一级目录，让路径回退到bin/的上一级，也就是工程根目录
 *
 * @param loops	播放次数，0和1：播放一次，小于0会导致崩溃
 * @param interrupt	是否中断音乐播放
 * @param music_finished 音乐播放结束回调
 * @return void
 */
static void play_mp3(const char *path,
                    int loops,
                    _Bool interrupt)
{
    const char *base = SDL_GetBasePath();
    printf("basePath: %s\n", base);//basePath: "F:\c\EmbeddedDevelopment\demos\lv_port_pc_vscode\bin\"
    if (is_empty_str(path))
    {
        printf("audio file path is empty\n");
        return;
    }
    if (loops < 0)
    {
        printf("audio play loops is negative\n");
        return;
    }
    if (isPlayingMusic())
    {
        if (interrupt)
        {
            //当前已有音乐正在播放，中断当前音乐的播放
            stopPlayingMusic();
        } else {
            printf("There is currently music playing, cancel play\n");
            //当前已有音乐正在播放，取消播放处理
            return;
        }
    }

    audio_init();

    playing_music = Mix_LoadMUS(path);
    if (!playing_music) {
        printf("Mix_LoadMUS error: %s\n", Mix_GetError());
        return;
    }

    Mix_PlayMusic(playing_music, loops);  // 播放次数

    //监听音乐播放结束回调，❌ Mix_HaltMusic() 不会触发 Mix_HookMusicFinished() 回调。
    Mix_HookMusicFinished(music_finished_callback);
}

/**
 * 单次播放mp3
 * @param path	mp3文件路径，例如"assets/audio/SmileThree.mp3"
 * @return void
 */
void single_play_mp3(const char *path)
{
    play_mp3(path, 1, false);
}

/**
 * 带有播放结束回调的单次播放mp3方法
 * @param path	mp3文件路径，例如"assets/audio/SmileThree.mp3"
 * @param music_finished	mp3播放结束回调
 * @return void
 */
void single_play_mp3_with_callback(const char *path,
                                   Mix_MusicFinishedCallback music_finished)
{
    //  * ❌SDL_mixer 在不同平台（macOS vs Windows）路径解析机制有差异：
    //  * MacOS系统传file相对路径"assets/audio/SmileThree.mp3"是从项目根目录开始，但在Windows系统对应路径实际为main.exe所在路径：
    //  * 即F:\c\EmbeddedDevelopment\lv_port_pc_vscode\bin，导致实际路径为：
    //  * F:\c\EmbeddedDevelopment\lv_port_pc_vscode\bin\assets\audio\SmileThree.mp3
    //  * ❌导致Windows系统Mix_LoadMUS方法解析path文件路径报错：Mix_LoadMUS error: Couldn't open 'assets/audio/SmileThree.mp3'
    //  * ✅解决办法：Windows系统传file相对路径需多加一个..表示上一级目录，让路径回退到bin/的上一级，也就是工程根目录
    char *prefix_path = "";
    if (is_windows_sys()) {
        prefix_path = "../";
    }
    char *fullPath = plus_str(prefix_path, path);
    printf("fullPath: %s", fullPath);
    if (music_finished != NULL)
    {
        play_finished_callback = music_finished;
    }
    play_mp3(fullPath, 1, false);
}
